package com.github.bobjoy.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NIOServer {

    /**
     * 获得一个ServerSocket通道，并对该通道做一些初始化工作
     */
    public void initServer(int port) throws IOException {

        // 1、创建ServerSocketChannel实例，并绑定指定端口；
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);// 设置通道为非阻塞
        serverChannel.socket().bind(new InetSocketAddress(port));// 将该通道对应的ServerSocket绑定到port端口

        // 2、创建Selector实例；
        Selector selector = Selector.open();

        // 3、将serverSocketChannel注册到selector，并指定事件OP_ACCEPT，最底层的socket通过channel和selector建立关联；
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端启动成功！");

        // 4、采用轮询的方式监听selector上是否有需要处理的事件，如果有，则进行处理
        while(true) {

            // 5、如果没有准备好的socket，select方法会被阻塞一段时间并返回0；
            int n = selector.select();
            if (n == 0) {
                continue;
            }

            // 6、如果底层有socket已经准备好，selector的select方法会返回socket的个数，
            // 而且selectedKeys方法会返回socket对应的事件（connect、accept、read or write）；
            Iterator ite = selector.selectedKeys().iterator();
            while(ite.hasNext()) {

                SelectionKey key = (SelectionKey) ite.next();
                ite.remove();// 删除已选的key，以防重复处理

                // 6、根据事件类型，进行不同的处理逻辑；
                if(key.isAcceptable()){// 客户请求连接事件

                    ServerSocketChannel server = (ServerSocketChannel) key.channel();

                    SocketChannel channel = server.accept();// 获得和客户端连接的通道
                    channel.configureBlocking(false);// 设置成非阻塞

                    // 在这里可以给客户端发送信息
                    channel.write(ByteBuffer.wrap("1234567890".getBytes()));

                    // 7、在和客户端连接成功之后，为了可以接收到客户端的信息，需要给通道设置读权限
                    channel.register(selector, SelectionKey.OP_READ);
                }else if(key.isReadable()){

                    /*
                     * 处理读取客户端发来的信息的事件
                     */

                    // 服务器可读取消息：得到事件发生的Socket通道
                    SocketChannel channel = (SocketChannel) key.channel();

                    // 创建读取的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(10);
                    channel.read(buffer);
                    byte[] data = buffer.array();
                    String msg = new String(data).trim();
                    System.out.println("服务端收到信息：" + msg);
                    ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
                    channel.write(outBuffer);//将消息回送给客户端
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8080);
    }
}
